<template>
  <div class="side-tree">
    <div class="side-tree-head">
      <ComboBox
        v-model="options.select"
        :data="options.selectOptions"
        :editable="false"
        class="grow-0"
      ></ComboBox>

      <div class="cursor-pointer shrink-0 flex items-center" @click="onRefresh">
        <ArkIconFont icon="icon-shuaxin" class="mr-1"></ArkIconFont>
        刷新
      </div>
    </div>

    <div class="px-2">
      <SearchBox
        placeholder="请输入"
        class="mx-auto"
        v-model="options.searchText"
        @search="fetchTreeData"
      >
        <Addon>
          <span
            v-if="options.searchText"
            class="textbox-icon icon-clear"
            @click="options.searchText = null"
          ></span>
        </Addon>
      </SearchBox>
    </div>

    <div class="flex-1 overflow-auto" v-loading="loading">
      <ArkScroll>
        <Tree
          ref="tree"
          :data="options.treeData"
          @selectionChange="onNodeSelectChange"
          @nodeExpand="onNodeExpand"
        >
          <template v-slot:icon="{ node }">
            <ArkIconFont
              class="text-gary2 shrink-0"
              :icon="node.iconCls"
              v-if="node.iconCls"
            />
            <span
              v-else
              class="tree-icon tree-folder shrink-0"
              :class="{
                'tree-file': !node.state,
              }"
            ></span>
          </template>

          <template v-slot="{ node }">
            {{ node.text }}
          </template>
        </Tree>
      </ArkScroll>
    </div>
  </div>
</template>

<script setup lang="ts">
import {computed, onBeforeMount, onBeforeUnmount, type PropType, ref, useTemplateRef, watch,} from "vue";
import _ from "lodash";
import ArkSideTreeOptionsDefault, {
  ArkSideTreeExpose,
  ArkSideTreeOptions,
  SelectOption,
  TreeData
} from "./ArkSideTreeOptions";
import ArkScroll from "../../basic/ark-scroll/ArkScroll.vue";
import ArkIconFont from "../../basic/ark-iconfont/ArkIconFont.vue";

/** 接口（组件的接口）。包括：defineOptions、defineModel、defineProps、defineEmits、defineExpose、defineSlots */
const options = defineModel("options", {
  type: Object as PropType<ArkSideTreeOptions>,
  default: () => ArkSideTreeOptionsDefault
});

defineExpose<ArkSideTreeExpose>({
  async fetchTreeData() {
    await fetchTreeData();
  },
});

/** 本地状态 (本地的响应式 property)。包括：ref、computed */
const treeRef = useTemplateRef("tree");
const loading = ref(false);

// 下拉选择对象
const dropdownSelectData = computed(() => {
  if (_.isNil(options.value.select)) {
    return null;
  }

  if (_.isNil(options.value.selectOptions) || options.value.selectOptions.length === 0) {
    return null;
  }

  const index = _.findKey(options.value.selectOptions, (item: SelectOption) => {
    return item.value === options.value.select;
  });

  if (_.isNil(index)) {
    return null;
  }

  return options.value.selectOptions[Number(index)]
})

/** 事件（通过响应式事件触发的回调）。包括：watch */
watch(
  () => {
    return options.value.select;
  },
  async (value) => {
    if (_.isNil(value)) {
      return
    }
    await onRefresh();
  },
);

watch(
  () => {
    return options.value.selectNode;
  },
  () => {
    const selectNode = options.value.selectNode;
    if (_.isEmpty(selectNode)) {
      clearAllTreeNodeSelected()
      return;
    }

    selectNode.forEach((node) => {
      treeRef.value.selectNode(node);
    });
  },
);

/** 生命周期钩子（按照它们被调用的顺序）。包括：onBeforeMount、onMounted、onBeforeUpdate、onUpdated、onBeforeUnmount、onUnmounted */
onBeforeMount(() => {
  init();
});
onBeforeUnmount(() => {
  options.value.searchText = "";
  options.value.treeData = [];
  options.value.selectNode = [];
});

/** 非响应式的 property (不依赖响应系统的实例 property)。例如：常规函数 */
const init = () => {
  _.defaultsDeep(options.value, ArkSideTreeOptionsDefault);

  // 默认选中第一个下拉选项。select变化会被watch监听到，避免重复触发fetchTreeData
  if (!options.value.select && options.value.selectOptions?.length > 0) {
    options.value.select = options.value.selectOptions[0].value;
    return
  }

  fetchTreeData();
};
const fetchTreeData = async () => {
  let defineTreeDataFetch = options.value.defineTreeDataFetch;
  const fetchTreeDataParams = [options.value.select, options.value.searchText]
  if (!_.isNil(dropdownSelectData.value?.defineTreeDataFetch)) {
    defineTreeDataFetch = dropdownSelectData.value.defineTreeDataFetch;
    fetchTreeDataParams.shift()
  }

  if (!defineTreeDataFetch) {
    return;
  }

  loading.value = true;
  await defineTreeDataFetch(...fetchTreeDataParams)
    .then((data) => {
      options.value.treeData = data;
    })
    .finally(() => {
      loading.value = false;
    });
};

const clearAllTreeNodeSelected = () => {
  if (!_.isEmpty(options.value.selectNode)) {
    return
  }

  const treeNodeSelectClass = 'tree-node-selected';
  const treeEl = treeRef.value?.$el;

  const selectedNodes = treeEl?.querySelectorAll(`.${treeNodeSelectClass}`);
  selectedNodes?.forEach((node: Element) => {
    node.classList.remove(treeNodeSelectClass);
  });
}

const onNodeSelectChange = (node: TreeData | TreeData[]) => {
  options.value.selectNode = Array.isArray(node) ? node : [node];

  // onNodeSelectChange
  let onNodeSelectChange = options.value.onNodeSelectChange;
  if (!_.isNil(dropdownSelectData.value?.onNodeSelectChange)) {
    onNodeSelectChange = dropdownSelectData.value.onNodeSelectChange;
  }
  if (!_.isNil(onNodeSelectChange)) {
    onNodeSelectChange(Array.isArray(node) ? node : [node]);
  }

  // afterNodeSelectChange
  let afterNodeSelectChange = options.value.afterNodeSelectChange;
  if (!_.isNil(dropdownSelectData.value?.afterNodeSelectChange)) {
    afterNodeSelectChange = dropdownSelectData.value.afterNodeSelectChange;
  }
  if (!_.isNil(afterNodeSelectChange)) {
    afterNodeSelectChange(Array.isArray(node) ? node : [node]);
  }
};
const onNodeExpand = (node: TreeData) => {
  let onNodeExpand = options.value.onNodeExpand;
  if (!_.isNil(dropdownSelectData.value?.onNodeExpand)) {
    onNodeExpand = dropdownSelectData.value.onNodeExpand;
  }

  if (!onNodeExpand) {
    return;
  }

  onNodeExpand(node);
};

const onRefresh = async () => {
  await fetchTreeData();
  onNodeSelectChange([]);
};

/** 其它 */
</script>

<style scoped lang="scss">
// @reference "../../lib/tailwindcss/index.css";

.side-tree {
  @apply flex flex-col;
}

.side-tree-head {
  @apply flex justify-start items-center;
  column-gap: 9px;
  padding: 4px 8px;
}

:deep(.tree-indent) {
  @apply shrink-0;
}
</style>
